package tc.de.krawallmann.gravoblocks.game.model;

import java.util.List;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Sound;
import org.newdawn.slick.state.StateBasedGame;

import tc.de.krawallmann.event.GameOverListener;
import tc.de.krawallmann.event.PauseListener;
import tc.de.krawallmann.extension.AchievementExtension;
import tc.de.krawallmann.extension.SettingsExtension;
import tc.de.krawallmann.gravoblocks.achievements.Achievements;
import tc.de.krawallmann.gravoblocks.game.GravoBlocksGame;
import tc.de.krawallmann.gravoblocks.util.pathfinding.AStar;

/**
 * The Class NormalGameplay encorporates the game rules for normal tetris-like
 * gameplay.
 * 
 * @author KrawallMann
 * @version 1.0
 */
public class PuzzleGamePlay extends AbstractTetrisGameRule {

	/** The delay to wait after a line is cleared. */
	private transient int afterLineDelay;
	/** The current state. */
	private transient States currentState = States.INITIALIZING;
	/** The hold state. */
	private transient HoldState holdState = HoldState.EMPTY;
	/** The triggers the gameover state only once. */
	private transient boolean onceGameOver;
	private transient int playtime;
	/** The start delay. */
	private transient int startDelay;
	private transient boolean soundOnce;

	/**
	 * Instantiates a new NormalGameplay.
	 * 
	 * @param achievements
	 *            the AchievementExtension to use
	 */
	public PuzzleGamePlay(final AchievementExtension achievements, final Sound rowSound) {
		super(achievements, rowSound);
	}

	public void checkEnclosedGaps() {
		for (int row = getTetris().getMainGrid().getHeight() - 1; row >= 0; row--) {
			if (hasEnclosedGap(row)) {
				for (final TetrisBlock b : getTetris().getMainGrid().getRow(row)) {
					b.setMetallic(true);
				}
			}
		}
	}

	/**
	 * Puts a tile into the hold grid - or doesn't, if it is not possible.
	 */
	public boolean enterHold() {
		switch (holdState) {
			case EMPTY: {
				getTetris().getMainGrid().removeTile(getTetris().getMainGrid().getCurrentTile());
				getTetris().getHoldGrid()
						.setCurrentTile(getTetris().getMainGrid().getCurrentTile());
				getTetris().getHoldGrid().insert(getTetris().getMainGrid().getCurrentTile(), 2, 2);
				getTetris().getMainGrid().removeGhosts();
				getTetris().getMainGrid().setCurrentTile(null);
				currentState = States.NEW_TILE;
				holdState = HoldState.REPLACED;
				return true;
			}
			case AVAILABLE: {
				getTetris().getMainGrid().removeGhosts();
				final TetrisTile current = getTetris().getMainGrid().getCurrentTile();
				getTetris().getMainGrid().removeTile(current);
				getTetris().getHoldGrid().removeTile(getTetris().getHoldGrid().getCurrentTile());
				getTetris().getMainGrid()
						.setCurrentTile(getTetris().getHoldGrid().getCurrentTile());
				getTetris().getMainGrid().insert(getTetris().getHoldGrid().getCurrentTile(), 6, 1);
				getTetris().getHoldGrid().setCurrentTile(current);
				getTetris().getHoldGrid().insert(current, 2, 2);
				moveGhost();
				holdState = HoldState.REPLACED;
				return true;
			}
			default: {
				// Invalid state
				break;
			}

		}
		return false;
	}

	/**
	 * Lets the tile fall
	 * 
	 * @param delta
	 *            the delta
	 * @param override
	 *            fall although the fall delay is not completed!
	 */
	public void fall() {
		if (getTetris().getMainGrid().move(getTetris().getMainGrid().getCurrentTile(), 0, 1)) {
			moveGhost();
		} else {
			checkEnclosedGaps();
			if (getFullRowAmount(4) == 0) {
				currentState = States.NEW_TILE;
			} else {
				markRows();
				currentState = States.WAIT_AFTER_LINES;
			}
			if (holdState == HoldState.REPLACED) {
				holdState = HoldState.AVAILABLE;
			}

		}
	}

	public int findNextBlocked(final boolean[][] field, final int row, final int xPosition) {
		if (!field[xPosition][row]) {
			return Integer.MIN_VALUE;
		}
		int xPos2;
		for (xPos2 = xPosition + 1; (xPos2 < field.length) && field[xPos2][row]; xPos2++) {

		}
		for (; (xPos2 < field.length) && !field[xPos2][row]; xPos2++) {

		}
		if (xPos2 >= field.length) {
			return Integer.MIN_VALUE;
		}
		return xPos2;
	}

	@Override
	public int getFullRowAmount(final int max) {
		int fullRows = 0;
		for (int row = getTetris().getMainGrid().getHeight() - 1; (row >= 0) && (fullRows < max); row--) {
			if (isRowFull(row)) {
				++fullRows;
			}
		}
		return fullRows;
	}

	/**
	 * Gets the points for a given amount of cleared rows.
	 * 
	 * @param newLines
	 *            the number of cleared lines
	 * @return the points
	 */
	@Override
	public int getPoints(final int newLines) {
		int ret = (getLevel() - 1) * 50;
		switch (newLines) {
			case 1: {
				ret = 50;
				break;
			}
			case 2: {
				ret = 150;
				break;
			}
			case 3: {
				ret = 1000;
				break;
			}
			case 4: {
				ret = 3500;
				break;
			}
			default: {
				ret = 0;
			}
		}
		return ret;
	}

	public boolean hasEnclosedGap(final int row) {
		final boolean[][] field = getTetris().getMainGrid().asBooleanArray();
		for (int x = 0; x < field.length; x++) {
			final int xPos2 = findNextBlocked(field, row, x);
			if (xPos2 == Integer.MIN_VALUE) {
				continue;
			}
			if (field[xPos2][row] && AStar.astar(field, x, row, xPos2, row, row)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isRowFull(final int row) {
		boolean ret = true;
		for (int i = 0; (i < getTetris().getMainGrid().getWidth()) && ret; i++) {
			final TetrisBlock block = getTetris().getMainGrid().getBlockAt(i, row);
			ret = block.isBlocked() && !block.isMetallic();
		}
		return ret;
	}

	@Override
	public void markRows() {
		List<TetrisBlock> thisRow;
		OuterLoop:
		for (int row = 0; row < getTetris().getMainGrid().getHeight(); row++) {
			thisRow = getTetris().getMainGrid().getRow(row);
			for (final TetrisBlock b : thisRow) {
				if (!b.isBlocked() || b.isMetallic()) {
					continue OuterLoop;
				}
			}
			for (final TetrisBlock b : thisRow) {
				b.setMarkedToRemove(true);
			}
		}
	}

	@Override
	public int removeFullRows() {
		int ret = 0;
		OuterLoop:
		for (int row = 0; row < getTetris().getMainGrid().getHeight(); row++) {
			final List<TetrisBlock> thisRow = getTetris().getMainGrid().getRow(row);
			for (int column = 0; column < getTetris().getMainGrid().getWidth(); column++) {
				if (thisRow.get(column).isMarkedToRemove()) {
					break;
				} else if (!thisRow.get(column).isBlocked() || thisRow.get(column).isMetallic()) {
					continue OuterLoop;
				}
			}
			for (final TetrisBlock b : thisRow) {
				b.setBlocked(false);
				b.setTransparent(false);
				b.setGhost(false);
				b.setMarkedToRemove(false);
			}
			++ret;
			for (int k = row; k > 0; k--) {
				final List<TetrisBlock> currentRow = getTetris().getMainGrid().getRow(k);
				final List<TetrisBlock> rowAbove = getTetris().getMainGrid().getRow(k - 1);
				for (int l = 0; l < getTetris().getMainGrid().getWidth(); l++) {
					currentRow.get(l).setBlocked(false);
					if (rowAbove.get(l).isBlocked()) {
						final BlockColor temp = rowAbove.get(l).getColor();
						rowAbove.get(l).setBlocked(false);
						currentRow.get(l).setColor(temp);
						currentRow.get(l).setBlocked(true);
					}
				}
			}
		}
		return ret;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * tc.de.krawallmann.gravoblocks.game.model.AbstractTetrisGameRule#reset()
	 */
	@Override
	public void reset() {
		super.reset();
		playtime = 0;
		soundOnce=false;
		startDelay = 0;
		onceGameOver = false;
		holdState = HoldState.EMPTY;
		currentState = States.INITIALIZING;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * tc.de.krawallmann.gravoblocks.game.model.IGameRule#update(org.newdawn
	 * .slick.GameContainer, org.newdawn.slick.state.StateBasedGame, int)
	 */
	@Override
	public void update(final GameContainer container, final StateBasedGame game, final int delta) {
		if (currentState != States.PAUSED) {
			playtime += delta;
			if (playtime > 300000&&!getAchievements().isUnlocked(Achievements.TWENTYMINUTES)) {
				getAchievements().unlockAchievement(Achievements.FIVEMINUTES);
			}
			if (playtime > 600000&&!getAchievements().isUnlocked(Achievements.TENMINUTES)) {
				getAchievements().unlockAchievement(Achievements.TENMINUTES);
			}
			if (playtime > 900000&&!getAchievements().isUnlocked(Achievements.FIFTEENMINUTES)) {
				getAchievements().unlockAchievement(Achievements.FIFTEENMINUTES);
			}
			if (playtime > 1200000&&!getAchievements().isUnlocked(Achievements.TWENTYMINUTES)){
				getAchievements().unlockAchievement(Achievements.TWENTYMINUTES);
			}
		}

		if (currentState == States.INITIALIZING) {
			initializeState();
		} else if (currentState == States.STARTING) {
			startingState(delta);
		} else if (currentState == States.NEW_TILE) {
			newTileState();
		} else if (currentState == States.MOVING) {
			movingState(delta);
		} else if (currentState == States.WAIT_AFTER_LINES) {
			waitAfterLinesState(delta);
		} else if (currentState == States.PAUSED) {
			pausedState();
		} else if (currentState == States.GAMEOVER) {
			gameOverState();
		}
	}

	/**
	 * Called if currentState==States.GAMEOVER.
	 */
	protected void gameOverState() {
		if (!onceGameOver) {
			setGameover(true);
			onceGameOver = true;
			for (final GameOverListener l : getGameOverListeners()) {
				l.onGameover();
			}
		}
	}

	/**
	 * Called if currentState==States.INITIALIZING.
	 */
	protected void initializeState() {
		currentState = States.STARTING;
	}

	protected boolean isBlocked(final int xPosition, final int yPosition) {
		if ((xPosition < 0) || (xPosition > getTetris().getMainGrid().getWidth())
				|| (yPosition < 0) || (yPosition > getTetris().getMainGrid().getWidth())) {
			return true;
		}
		return getTetris().getMainGrid().getBlockAt(xPosition, yPosition).isBlocked()
				|| getTetris().getMainGrid().getBlockAt(xPosition, yPosition).isMetallic();
	}

	/**
	 * Called if currentState==States.MOVING.
	 * 
	 * @param delta
	 *            the delta
	 */
	protected void movingState(final int delta) {
		setMoveLeftKeyDelay(getMoveLeftKeyDelay() - delta);
		setMoveRightKeyDelay(getMoveRightKeyDelay() - delta);
		setFallKeyDelay(getFallKeyDelay() - delta);
		boolean hold = isHoldRequest();
		setHoldRequest(false);
		if (hold && (holdState != HoldState.REPLACED)) {
			hold &= enterHold();
			getAchievements().unlockAchievement(Achievements.USEDHOLD);
		}
		if (!hold) {
			if ((getMoveLeftKeyDelay() < 0) && isMoveLeftRequest()) {
				setMoveLeftKeyDelay(Math.max(100, 140 - getLeftTimes() * 10));
				setRightTimes(0);
				setLeftTimes(getLeftTimes() + 1);
				setMoveRightKeyDelay(150);
				getTetris().getMainGrid().move(getTetris().getMainGrid().getCurrentTile(), -1, 0);
				moveGhost();
			} else if ((getMoveRightKeyDelay() < 0) && isMoveRightRequest()) {
				setMoveRightKeyDelay(Math.max(100, 140 - getRightTimes() * 10));
				setLeftTimes(0);
				setRightTimes(getRightTimes() + 1);
				setMoveLeftKeyDelay(150);
				getTetris().getMainGrid().move(getTetris().getMainGrid().getCurrentTile(), 1, 0);
				moveGhost();
			} else if (isRotateLeft()) {
				setRotateLeft(false);
				getTetris().getMainGrid().rotate(getTetris().getMainGrid().getCurrentTile(), +1);
				moveGhost();
			} else if (isRotateRight()) {
				setRotateRight(false);
				getTetris().getMainGrid().rotate(getTetris().getMainGrid().getCurrentTile(), -1);
				moveGhost();
			} else if (isFalldownRequest()) {
				setFalldownRequest(false);
				boolean canMove = true;
				while (canMove) {
					canMove =
							getTetris().getMainGrid().move(
									getTetris().getMainGrid().getCurrentTile(), 0, 1);
				}
				fall();
			} else if (isPauseRequest()) {
				setPauseRequest(false);
				currentState = States.PAUSED;
				for (final PauseListener pause : getPauseListeners()) {
					pause.pauseStateChanged(true);
				}
			} else {
				if ((getFallKeyDelay() < 0) && isFallRequest()) {
					setFallKeyDelay(Math.max(50, 100 - getFallTimes() * 10));
					setFallTimes(getFallTimes() + 1);
					if (getTetris().getMainGrid().move(getTetris().getMainGrid().getCurrentTile(),
							0, 1)) {
						moveGhost();
					} else {
						setFallKeyDelay(100);
						fall();
					}
				}
			}
		}
	}

	/**
	 * Called if currentState==States.NEW_TILE.
	 */
	protected void newTileState() {
		if ((getTetris().getMainGrid().getCurrentTile() == null)
				&& (holdState != HoldState.REPLACED)) {
			getTetris().getMainGrid().setCurrentTile(TileFactory.generateRandomTile(true));
			getTetris().getPreviewGrid().setCurrentTile(TileFactory.generateRandomTile(true));
		} else {
			getTetris().getMainGrid().setCurrentTile(getTetris().getPreviewGrid().getCurrentTile());
			getTetris().getPreviewGrid().reset();
			getTetris().getPreviewGrid().setCurrentTile(TileFactory.generateRandomTile(true));
		}
		getTetris().getPreviewGrid().insert(getTetris().getPreviewGrid().getCurrentTile(), 2, 2);
		if (getTetris().getMainGrid().insert(getTetris().getMainGrid().getCurrentTile(), 6, 1)) {
			currentState = States.MOVING;
			moveGhost();
			setFallKeyDelay(150);
		} else {
			currentState = States.GAMEOVER;
		}
	}

	/**
	 * Called if currentState==States.PAUSED.
	 */
	protected void pausedState() {
		if (isPauseRequest()) {
			setPauseRequest(false);
			currentState = States.MOVING;
			for (final PauseListener pause : getPauseListeners()) {
				pause.pauseStateChanged(false);
			}
		}
	}

	/**
	 * Called if currentState==States.STARTING.
	 * 
	 * @param delta
	 *            the delta
	 */
	protected void startingState(final int delta) {
		startDelay += delta;
		if (startDelay > AbstractTetrisGameRule.WAIT_FOR_START) {
			startDelay = 0;
			getAchievements().unlockAchievement(Achievements.FIRSTGAME);
			currentState = States.NEW_TILE;
		}
	}

	/**
	 * Called if currentState==States.WAIT_AFTER_LINES.
	 * 
	 * @param delta
	 *            the delta
	 */
	protected void waitAfterLinesState(final int delta) {
		afterLineDelay += delta;
		if (SettingsExtension.getSetting(GravoBlocksGame.USE_SOUND) && !soundOnce) {
			soundOnce = true;
			getRowSound().play();
		}
		if (afterLineDelay > AbstractTetrisGameRule.WAIT_FOR_ROW) {
			afterLineDelay = 0;
			final int rows = removeFullRows();
			setClearedRows(getClearedRows() + rows);
			setLevel(1 + getClearedRows() / 7);
			if (getClearedRows() >= 10) {
				getAchievements().unlockAchievement(Achievements.TENLINES);
			}
			if (getClearedRows() >= 30) {
				getAchievements().unlockAchievement(Achievements.THIRTY);
			}
			if (getClearedRows() >= 50) {
				getAchievements().unlockAchievement(Achievements.FIFTYLINES);
			}
			if (getClearedRows() >= 100) {
				getAchievements().unlockAchievement(Achievements.LINES100);
			}
			if (getClearedRows() >= 150) {
				getAchievements().unlockAchievement(Achievements.LINES150);
			}
			if (getClearedRows() >= 250) {
				getAchievements().unlockAchievement(Achievements.LINES250);
			}

			setScore(getScore() + getPoints(rows));
			currentState = States.NEW_TILE;
		}
	}

}
